Amazon Aurora DSQLが発表されたのでdrizzleから繋いでみた #AWSreInvent
製造ビジネステクノロジー部のやまたつです!
re:Invent 2024でpostgres互換のサーバーレス分散SQLデータベースであるAmazon Aurora DSQLが発表されました!
なので早速サーバーレス指向ORMであるdrizzle ORMを使ってmigrationと接続を試してみました!
コードは以下のリポジトリに置いてあります。
前提
基本的な設定はdrizzle公式のpostgresqlのドキュメントに従っています。
公式通りの実装では動作しなかった点を中心に、以下に示していきます。
実装
src/db/schema.ts
まずはスキーマを定義します。
drizzleのGet Startedではid列の定義としてinteger().primaryKey().generatedAlwaysAsIdentity(),
を使っているのですが、Aurora DSQLはGENERATED ALWAYS AS IDENTITY
が使えないようで、以下のようなエラーが発生してしまいます。
error: IDENTITY constraint is not supported
なので代わりにvarchar().primaryKey().$default(() => randomUUID())
を使うようにしました。
(後で結局.$default(() => randomUUID())
は使わないコードになってしまったが。。)
import { randomUUID } from "node:crypto";
import { integer, pgTable, varchar } from "drizzle-orm/pg-core";
export const usersTable = pgTable("users", {
id: varchar().primaryKey().$default(() => randomUUID()),
name: varchar({ length: 255 }).notNull(),
age: integer().notNull(),
email: varchar({ length: 255 }).notNull().unique(),
});
drizzle.config.ts
次にdrizzleの接続設定です。
Aurora DSQLでは、15分ごとに有効期限切れになるAuthentication tokenをpasswardとして使う必要があります。
参考: https://docs.aws.amazon.com/aurora-dsql/latest/userguide/SECTION_authentication-token.html
.env
上で簡単に交換できるように、databaseUrl形式ではなく、host,database,user,passwordそれぞれを指定する形式に変更しました。
(prismaではこれができないのがめんどくさいんよな。。。)
import "dotenv/config";
import { defineConfig } from "drizzle-kit";
export default defineConfig({
out: "./drizzle",
schema: "./src/db/schema.ts",
dialect: "postgresql",
dbCredentials: {
host: process.env.DB_HOST!,
database: process.env.DB_NAME!,
user: process.env.DB_USER!,
password: process.env.DB_PASSWORD!,
ssl: true,
},
});
.env
以下のように環境変数を定義します。
Authentication tokenは公式ドキュメントを参考にwebコンソールから取得しました。
(aws cliで取得しようとしたけど、筆者が試した限りでは、更新してもまだaws dsql
が生えてなかった。。。)
DB_HOST=xxxxxxxxxxxxxxxxxxxxxxxxxx.dsql.us-east-1.on.aws
DB_USER=admin
DB_NAME=postgres
DB_PASSWORD='<<手に入れたAuthentication token>>'
migrationしてみる
$ npx drizzle-kit push
[✓] Pulling schema from database...
[✓] Changes applied
migrationできました!
データを追加したり参照したりしてみる
src/index.ts
を以下のように記述します。
何度も実行できるようにupsertで実装しました。
insertとselectはそれぞれconsole.time()
を使って計測してみます。
import "dotenv/config";
import { drizzle } from "drizzle-orm/node-postgres";
import { usersTable } from "./db/schema";
const db = drizzle({
connection: {
host: process.env.DB_HOST,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
ssl: true,
},
});
main().then(
() => {
console.info("Done");
process.exit(0);
},
(err) => {
console.error(err);
process.exit(1);
},
);
async function main() {
const users = [
{
id: "979d1120-db93-4c5e-b631-36f00fd46607",
name: "John Doe",
age: 30,
email: "foo@example.com",
},
{
id: "b35efca4-e902-4531-aad2-97d86e553582",
name: "Jane Doe",
age: 25,
email: "bar@example.com",
},
{
id: "b10c2fd5-be46-4b15-bd50-2043dcd394b3",
name: "Alice",
age: 20,
email: "buz@example.com",
},
] satisfies (typeof usersTable.$inferInsert)[];
for (const user of users) {
console.time("Inserting a user");
await db.insert(usersTable).values(user).onConflictDoUpdate({
target: usersTable.id,
set: user,
});
console.timeEnd("Inserting a user");
}
console.time("Selecting all users");
const selected = await db.select().from(usersTable);
console.timeEnd("Selecting all users");
console.log("Users:", selected);
}
それでは実行してみます。
$ pnpm tsx ./src/index.ts
Inserting a user: 793.159ms
Inserting a user: 85.274ms
Inserting a user: 81.759ms
Selecting all users: 87.706ms
Users: [
{
id: '979d1120-db93-4c5e-b631-36f00fd46607',
name: 'John Doe',
age: 30,
email: 'foo@example.com'
},
{
id: 'b10c2fd5-be46-4b15-bd50-2043dcd394b3',
name: 'Alice',
age: 20,
email: 'buz@example.com'
},
{
id: 'b35efca4-e902-4531-aad2-97d86e553582',
name: 'Jane Doe',
age: 25,
email: 'bar@example.com'
}
]
Done
保存されたデータが取得されました!
初回のinsertはコネクションの確立があるためか、1秒弱かかりますが、2回目以降は100ms以下で実行できていました。
ちゃんとした計測ではないので、もう少し詳しく計測してみたいですね。
まとめ
「drizzleでRDS Data APIを触るの、試してみたいなー」と思っていたところに今回の発表があったので、早速試してみました。
cold start時のオーバーヘッドが気になりますが、lambdaからも使ってみたいと思いました!
以上でした!